package org.softmed.rest.admin.file;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.dnd.DnDConstants;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.text.Position.Bias;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.softmed.filehandling.FileUtil;

public class FileBrowser extends JPanel implements TreeModelListener,
		TreeExpansionListener, TreeWillExpandListener, TreeSelectionListener,
		MouseListener {

	int width = 240;
	int height = 690;

	OpenFileListener openFileListener;
	SelectionListener selectionListener;

	Map<String, CustomFileNode> pathsToNames = new HashMap<String, CustomFileNode>();

	JTextField path = new JTextField();

	FileManager fileManager;
	JScrollPane scroll;
	// private DefaultMutableTreeNode root;
	DirectoryNode root;
	private JTree tree;
	private DefaultTreeModel model;
	ActionPanel2 actionPanel;
	TreeDragSource ds;
	TreeDropTarget dt;
	private ArrayList<String> expandedNodes;
	String filename;

	FileUtil util = new FileUtil();
	private String rootDir;

	public FileBrowser(String filename) {
		this.filename = filename;
		// path.setEditable(false);
		actionPanel = new ActionPanel2(this);
		setBackground(Color.BLACK);
		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

		JPanel pathPanel = new JPanel();
		pathPanel.setLayout(new BoxLayout(pathPanel, BoxLayout.LINE_AXIS));
		// pathPanel.set
		pathPanel.add(path);
		add(pathPanel);
		// add(path);

		path.setEditable(false);
		path.setEnabled(true);

		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
		panel.add(actionPanel);
		panel.add(Box.createHorizontalGlue());

		add(panel);

		try {
			rootDir = util.readFromFile(filename);
			if (rootDir.endsWith("\n"))
				rootDir = rootDir.substring(0, rootDir.indexOf('\n'));
		} catch (Throwable t) {
			t.printStackTrace();
			rootDir = util.getRootPath();

		}
		actionPanel.getSetRootFolder().setToolTipText(
				"Change root folder. Current root: " + rootDir);

		root = new DirectoryNode(rootDir);
		root.setUserObject("root");

		tree = new AutoScrollingJTree(root);
		ds = new TreeDragSource(tree, DnDConstants.ACTION_COPY_OR_MOVE);
		dt = new TreeDropTarget(tree, this);
		tree.setEditable(true);
		tree.setShowsRootHandles(true);
		model = (DefaultTreeModel) tree.getModel();
		model.addTreeModelListener(this);
		tree.addTreeExpansionListener(this);
		tree.addTreeWillExpandListener(this);
		tree.addTreeSelectionListener(this);
		tree.addMouseListener(this);

		scroll = new JScrollPane(tree);
		add(scroll);

		setMinimumSize(new Dimension(width, height));
		setMaximumSize(new Dimension(width, height));
		setPreferredSize(new Dimension(width, height));

		scroll.setMinimumSize(new Dimension(width, height - 55));
		// scroll.setMaximumSize(new Dimension(width, height - 55));
		scroll.setPreferredSize(new Dimension(width, height - 55));

		actionPanel.setMinimumSize(new Dimension(width, 25));
		actionPanel.setMaximumSize(new Dimension(width, 25));
		actionPanel.setPreferredSize(new Dimension(width, 25));

		path.setMinimumSize(new Dimension(width, 25));
		// path.setMaximumSize(new Dimension(width, 25));
		path.setPreferredSize(new Dimension(width, 25));

		pathPanel.setMinimumSize(new Dimension(width, 25));
		pathPanel.setMaximumSize(new Dimension(width, 25));
		pathPanel.setPreferredSize(new Dimension(width, 25));

		path.setToolTipText("Currently selected file path");

	}

	public String getCurrentSelectedNodeRelativePath() {
		CustomFileNode node = getCurrentSelectedNode();
		if (node == null)
			return null;

		String fullPath = node.getFullPath();
		fullPath = fullPath.substring(rootDir.length() + 1);
		return fullPath;
	}

	public String getRootfolder() {
		return rootDir;
	}

	public void setRootFolder(String folder) {
		rootDir = folder;
		root.setFullPath(rootDir);
		actionPanel.getSetRootFolder().setToolTipText(
				"Change root folde. Current root: " + folder);

		refresh();
		repaint();
	}

	public void saveRootDirectory() throws Throwable {
		util.saveToFile(filename, rootDir);
	}

	public FileManager getFileManager() {
		return fileManager;
	}

	public void setFileManager(FileManager fileManager) {
		this.fileManager = fileManager;
		dt.setFilemanager(fileManager);
		refresh();
	}

	protected List<String> getExpandedNodes() {

		expandedNodes = new ArrayList<String>();

		return recursiveGetExpandedNode(new TreePath(root.getPath()),
				expandedNodes);

	}

	protected List<String> recursiveGetExpandedNode(TreePath treeNode,
			List<String> expandedNodes) {
		Enumeration<TreePath> expanded = tree.getExpandedDescendants(treeNode);
		TreePath treePath = null;

		if (expanded != null)
			while (expanded.hasMoreElements()) {
				treePath = expanded.nextElement();
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath
						.getLastPathComponent();
				if (node instanceof CustomFileNode)
					expandedNodes.add(((CustomFileNode) node).getFullPath());

				// if (node instanceof DirectoryNode) {
				// recursiveGetExpandedNode(new TreePath(node.getPath()),
				// expandedNodes);
				// }
			}

		return expandedNodes;
	}

	public CustomFileNode getNode(String fullPath) {
		return pathsToNames.get(fullPath);
	}

	private void expandNodes() {
		// synchronized (this) {
		CustomFileNode cnode = null;
		List<String> toRemove = new ArrayList<String>();

		Collections.sort(expandedNodes, String.CASE_INSENSITIVE_ORDER);

		for (String fullPath : expandedNodes) {
			cnode = getNode(fullPath);
			if (cnode != null) {
				tree.expandPath(new TreePath(cnode.getPath()));
				toRemove.add(fullPath);
			}
		}

		expandedNodes.removeAll(toRemove);
		// }

	}

	public void testExpand() {
		// tree.expandPath(new TreePath(cnode.getPath()));
	}

	public void refresh() {
		// TODO keep the expanded state of the tree
		actionPanel.setButtonsEnabled(false);
		getExpandedNodes();
		pathsToNames.clear();
		pathsToNames.put(root.getFullPath(), root);

		try {
			showDirectoryContents(root, rootDir);
		} catch (Throwable e) {
			e.printStackTrace();
		}

		model.reload();
		tree.expandPath(new TreePath(root.getPath()));
		expandNodes();

	}

	private boolean showDirectoryContents(DirectoryNode root) throws Throwable {
		String path = root.getFullPath();

		pathsToNames.put(root.getFullPath(), root);

		root.setLoaded(true);
		if (!showDirectoryContents(root, path)) {
			root.setLoaded(false);
			return false;
		}

		return true;
	}

	protected boolean showDirectoryContents(DirectoryNode root, String path)
			throws Throwable {
		root.removeAllChildren();

		DirectoryContents content = fileManager.getDirectoryContent(path);

		List<String> directories = content.getDirectoryList();
		DirectoryNode directory = null;
		int count = 0;
		for (String string : directories) {
			directory = new DirectoryNode(string);
			pathsToNames.put(directory.getFullPath(), directory);
			root.add(directory);
			count++;
		}

		List<String> files = content.getFileList();
		FileNode file = null;
		for (String string : files) {
			file = new FileNode(string);
			pathsToNames.put(file.getFullPath(), file);
			root.add(file);
			count++;
		}

		// expandNodes();

		return count > 0;

	}

	@Override
	public void treeNodesChanged(TreeModelEvent arg0) {

		DefaultMutableTreeNode parent = (DefaultMutableTreeNode) arg0
				.getTreePath().getLastPathComponent();

		int index = arg0.getChildIndices()[0];
		DefaultMutableTreeNode node = (DefaultMutableTreeNode) (parent
				.getChildAt(index));

		String newName = (String) node.getUserObject();

		if (!(node instanceof CustomFileNode))
			return;

		CustomFileNode cnode = (CustomFileNode) node;

		try {

			String fullPath = cnode.getFullPath();

			pathsToNames.remove(fullPath);

			fileManager.rename(fullPath, newName);

			String newPath = cnode.getDirPath() + newName;
			cnode.setFullPath(newPath);

			pathsToNames.put(newPath, cnode);

			renameChildren(cnode);

		} catch (Throwable e) {
			e.printStackTrace();
			cnode.setFullPath(cnode.getFullPath());
		}
	}

	private void renameChildren(CustomFileNode cnode) {
		Enumeration children = cnode.children();
		while (children.hasMoreElements()) {
			CustomFileNode child = (CustomFileNode) children.nextElement();
			child.setDirPath(cnode.getFullPath());
			renameChildren(child);
		}

	}

	@Override
	public void treeNodesInserted(TreeModelEvent arg0) {
		// expandNodes();

	}

	@Override
	public void treeNodesRemoved(TreeModelEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void treeStructureChanged(TreeModelEvent arg0) {
		// expandNodes();

	}

	// TREE EXPANSION LISTENER
	@Override
	public void treeCollapsed(TreeExpansionEvent arg0) {
		Object node = arg0.getPath().getLastPathComponent();
		if (!(node instanceof CustomFileNode))
			return;

		CustomFileNode cnode = (CustomFileNode) node;
		String path = cnode.getFullPath();

		Set<String> keys = pathsToNames.keySet();
		List<String> toRemove = new ArrayList<String>();
		for (String string : keys) {
			if (string.startsWith(path))
				toRemove.add(string);
		}

		for (String string2 : toRemove) {
			pathsToNames.remove(string2);
		}

		pathsToNames.put(cnode.getFullPath(), cnode);

	}

	@Override
	public void treeExpanded(TreeExpansionEvent arg0) {
		// Object node = arg0.getPath().getLastPathComponent();
		// if( ! (node instanceof DirectoryNode))
		// return ;
		//		
		// DirectoryNode directory = (DirectoryNode) node;
		//		
		// showDirectoryContents(directory);

	}

	@Override
	public void treeWillCollapse(TreeExpansionEvent arg0)
			throws ExpandVetoException {
		// TODO Auto-generated method stub

	}

	@Override
	public void treeWillExpand(TreeExpansionEvent arg0)
			throws ExpandVetoException {
		Object node = arg0.getPath().getLastPathComponent();
		if (!(node instanceof DirectoryNode))
			return;

		DirectoryNode directory = (DirectoryNode) node;

		try {
			showDirectoryContents(directory);
			// throw new ExpandVetoException(arg0);
		} catch (Throwable t) {
			t.printStackTrace();
			throw new ExpandVetoException(arg0);
		}

	}

	@Override
	public void valueChanged(TreeSelectionEvent arg0) {

		if (!arg0.isAddedPath())
			return;

		Object node = arg0.getPath().getLastPathComponent();

		if (node instanceof CustomFileNode) {
			CustomFileNode directory = (CustomFileNode) node;
			path.setText(directory.getFullPath());

			if (node instanceof FileNode)
				actionPanel.enableFileFunctions();
			else
				actionPanel.enableDirectoryFunctions();

			if (selectionListener != null)
				selectionListener.selectionChanged(directory);

		} else
			actionPanel.enableDirectoryFunctions();

	}

	public CustomFileNode getCurrentSelectedNode() {
		TreePath tempPath = tree.getSelectionPath();
		if (tempPath == null)
			return null;

		CustomFileNode node = (CustomFileNode) tempPath.getLastPathComponent();
		return node;
	}

	public String getCurrentSelectedNodeName() {
		CustomFileNode node = getCurrentSelectedNode();
		if (node == null)
			return null;

		return node.getName();
	}

	public String getCurrentSelectedNodePath() {
		CustomFileNode node = getCurrentSelectedNode();
		if (node == null)
			return null;

		return node.getFullPath();
	}

	public String getCurrentSelectedNodeDirectoryPath() {
		CustomFileNode node = getCurrentSelectedNode();
		if (node == null)
			return null;

		return node.getDirPath();
	}

	public void deleteSelection() {
		CustomFileNode node = (CustomFileNode) tree.getSelectionPath()
				.getLastPathComponent();
		try {
			fileManager.remove(node.getFullPath());
			pathsToNames.remove(node.getFullPath());
			expandedNodes.remove(node.getFullPath());

			DirectoryNode dir = (DirectoryNode) node.getParent();

			model.removeNodeFromParent(node);

			if (dir.children() == null || !dir.children().hasMoreElements())
				dir.setLoaded(false);

		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public Map<String, CustomFileNode> getPathsToNames() {
		return pathsToNames;
	}

	public String createDirectory(String parent, String name) throws Throwable {
		String fullPath = parent + "/" + name;
		fileManager.createDirectory(fullPath);
		CustomFileNode parentNode = getNode(parent);
		DirectoryNode directory = new DirectoryNode(fullPath);
		pathsToNames.put(directory.getFullPath(), directory);
		model.insertNodeInto(directory, parentNode, 0);
		TreeNode[] nodePath = directory.getPath();
		tree.expandPath(new TreePath(nodePath));
		refresh();
		return directory.getFullPath();
	}

	public String createFile(String parent, String filename) throws Throwable {
		String fullPath = null;
		if (!parent.endsWith("/") && !filename.startsWith("/"))
			fullPath = parent + '/' + filename;

		// fullPath = parent + filename;

		fileManager.createFile(fullPath);
		// String searchParent = parent.substring(0, parent.length()-1);

		CustomFileNode parentNode = getNode(parent);
		FileNode file = new FileNode(fullPath);
		pathsToNames.put(file.getFullPath(), file);
		model.insertNodeInto(file, parentNode, 0);
		TreeNode[] nodePath = file.getPath();
		tree.expandPath(new TreePath(nodePath));
		refresh();
		return file.getFullPath();
	}

	public OpenFileListener getOpenFileListener() {
		return openFileListener;
	}

	public void setOpenFileListener(OpenFileListener openFileListener) {
		this.openFileListener = openFileListener;
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {
		int selRow = tree.getRowForLocation(e.getX(), e.getY());
		TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
		if (selPath == null)
			return;
		CustomFileNode cnode = (CustomFileNode) selPath.getLastPathComponent();
		if (selRow != -1) {
			if (e.getClickCount() == 2 && cnode instanceof FileNode) {
				// System.out.println("Double Click !!!");
				if (openFileListener != null)
					openFileListener.fileOpened(cnode.getFullPath());
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	public SelectionListener getSelectionListener() {
		return selectionListener;
	}

	public void setSelectionListener(SelectionListener selectionListener) {
		this.selectionListener = selectionListener;
	}

	public String createDirectoryWithPopup() {
		return actionPanel.createDirectory();
	}

	public String createFileWithPopup() {
		return actionPanel.createFile();
	}

}
